<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="zpjaB" id="zpjaB"><span data-lake-id="u845b871d" id="u845b871d">典型回答</span></h1>
  <p data-lake-id="u3d4c4167" id="u3d4c4167"><br></p>
  <p data-lake-id="ucd48aba8" id="ucd48aba8"><span data-lake-id="uff4f15fc" id="uff4f15fc">MySQL 外键（Foreign Key）是用于建立表之间关系的，它定义了一个表中的一列或一组列，这些列的值必须在另一个表的主键列中存在。</span></p>
  <p data-lake-id="u3c4f2833" id="u3c4f2833"><span data-lake-id="udd21964b" id="udd21964b">​</span><br></p>
  <p data-lake-id="u6814b876" id="u6814b876"><span data-lake-id="u4e3fe926" id="u4e3fe926">MySQL 外键最大的作用就是有助于维护数据的一致性和完整性。</span></p>
  <p data-lake-id="uaade1d4b" id="uaade1d4b"><span data-lake-id="u34f30aa2" id="u34f30aa2">​</span><br></p>
  <ul list="u4c8ee8bc">
   <li fid="u4e73cfcd" data-lake-id="ube0007ce" id="ube0007ce"><span data-lake-id="u32f32326" id="u32f32326">一致性：如果一个订单表引用了一个客户表的外键，外键可以确保订单的客户 ID 存在于客户表中，从而保持数据的一致性。</span></li>
   <li fid="u4e73cfcd" data-lake-id="u10ba932a" id="u10ba932a"><span data-lake-id="uce49dcfa" id="uce49dcfa">完整性：外键可以防止在引用表中删除正在被其他表引用的记录，从而维护数据的完整性。</span></li>
  </ul>
  <p data-lake-id="u0cad46ac" id="u0cad46ac"><span data-lake-id="u63aecc65" id="u63aecc65">​</span><br></p>
  <p data-lake-id="uf624843f" id="uf624843f"><span data-lake-id="u9a0e7aaa" id="u9a0e7aaa">但是，其实在很多大型互联网公司中，很少用外键的，甚至阿里巴巴Java开发手册中明确规定了：</span></p>
  <p data-lake-id="u46e98a78" id="u46e98a78"><span data-lake-id="u8d3dd0ba" id="u8d3dd0ba">​</span><br></p>
  <blockquote data-lake-id="u37e3fc67" id="u37e3fc67">
   <p data-lake-id="ue7d1fcee" id="ue7d1fcee"><strong><span data-lake-id="ud50dccb1" id="ud50dccb1" style="color: rgb(99, 99, 99)">【强制】不得使用外键与级联，一切外键概念必须在应用层解决。</span></strong></p>
   <p data-lake-id="u79a83d5e" id="u79a83d5e"><strong><span data-lake-id="u2a5c9c01" id="u2a5c9c01" style="color: rgb(99, 99, 99)">​</span></strong><br></p>
   <p data-lake-id="ufdb96de9" id="ufdb96de9"><strong><span data-lake-id="uf92612a6" id="uf92612a6" style="color: rgb(99, 99, 99)"> 说明: 以学生和成绩的关系为例，学生表中的 student_id 是主键，那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id，同时触发成绩表中的 student_id 更新，即为级联更新。外键与级联更新适用于单机低并发，不适合分布式、高并发集群;级联更新是强阻塞，存在数据库更新风暴的风险;外键影响数据库的插入速度。</span></strong></p>
  </blockquote>
  <p data-lake-id="ud58d5843" id="ud58d5843"><br></p>
  <p data-lake-id="u0f3b400c" id="u0f3b400c"><span data-lake-id="u4f8b9bd6" id="u4f8b9bd6">那么，使用外键会带来哪些问题呢？</span></p>
  <p data-lake-id="u66bbbf66" id="u66bbbf66"><span data-lake-id="u8f512148" id="u8f512148">​</span><br></p>
  <p data-lake-id="u443f3140" id="u443f3140"><span data-lake-id="u0a50ffe5" id="u0a50ffe5">先举个例子，我们有两张表：Orders（订单）和 OrderItems（订单项）。这两个表之间通过外键建立关系，订单项表中的外键引用订单表的订单号。</span></p>
  <p data-lake-id="u2974e46a" id="u2974e46a"><span data-lake-id="ub89dd0bc" id="ub89dd0bc">​</span><br></p>
  <pre lang="java"><code>
CREATE TABLE Orders (
    OrderID INT PRIMARY KEY,
    CustomerID INT,
    OrderDate DATE,
    -- 其他订单信息
);

CREATE TABLE OrderItems (
    ItemID INT PRIMARY KEY,
    OrderID INT,
    ProductID INT,
    Quantity INT,
    -- 其他订单项信息
    FOREIGN KEY (OrderID) REFERENCES Orders(OrderID)
);
</code></pre>
  <p data-lake-id="ua6e552b5" id="ua6e552b5"><span data-lake-id="u3364994b" id="u3364994b">​</span><br></p>
  <p data-lake-id="u1d6ba225" id="u1d6ba225"><span data-lake-id="u182eee01" id="u182eee01">接下来基于这两张表展开分析，</span></p>
  <h3 data-lake-id="hzf6E" id="hzf6E"><span data-lake-id="ub9cf7d78" id="ub9cf7d78">性能问题</span></h3>
  <p data-lake-id="u8b02cbdd" id="u8b02cbdd"><br></p>
  <p data-lake-id="uccc2daef" id="uccc2daef"><span data-lake-id="u47c9af57" id="u47c9af57">首先就是性能问题，因为外键会增加数据库的维护负担，因为每次插入、更新或删除数据时，数据库都需要检查外键约束的完整性。</span></p>
  <p data-lake-id="u7ad33092" id="u7ad33092"><br></p>
  <p data-lake-id="u2846abbf" id="u2846abbf"><span data-lake-id="ub2a56383" id="ub2a56383">首先，这两张表中共有两个索引，一个是Orders表的主键索引，一个是OrdersItems表的外键索引，这就使得每次插入、更新或删除订单或订单项时，数据库需都要维护这两个索引，这可能会导致性能开销。</span></p>
  <p data-lake-id="u5a414c71" id="u5a414c71"><span data-lake-id="ucfea05e3" id="ucfea05e3">​</span><br></p>
  <p data-lake-id="udcb6304e" id="udcb6304e"><span data-lake-id="u25195c82" id="u25195c82">其次，在插入新的订单项之前，数据库需要执行数据一致性检查以确保引用的订单号在 Orders 表中存在。这额外的检查可能增加插入订单项的执行时间。</span></p>
  <p data-lake-id="u96aa3dc9" id="u96aa3dc9"><span data-lake-id="u1eae7c1d" id="u1eae7c1d">​</span><br></p>
  <h3 data-lake-id="ounzq" id="ounzq"><span data-lake-id="u7cf53a94" id="u7cf53a94">锁竞争问题</span></h3>
  <p data-lake-id="uba4c4297" id="uba4c4297"><br></p>
  <p data-lake-id="u69951fff" id="u69951fff"><span data-lake-id="ufbe394dd" id="ufbe394dd">还有就是比较容易忽略的锁竞争问题。当多个事务同时尝试插入或更新订单项时，它们就需要去检查订单表，就需要获得额外的锁，以确保一致性。这可能导致事务之间的锁竞争，降低并发性能。</span></p>
  <p data-lake-id="u9ee00dbb" id="u9ee00dbb"><span data-lake-id="uc6b7fc99" id="uc6b7fc99">​</span><br></p>
  <p data-lake-id="u081ad9a7" id="u081ad9a7"><span data-lake-id="ucf98203e" id="ucf98203e">一旦有了锁竞争，就可能带来更加严重的死锁问题，所以都是需要尽量避免的。</span></p>
  <p data-lake-id="ua275ebb9" id="ua275ebb9"><span data-lake-id="ucebc59c7" id="ucebc59c7">​</span><br></p>
  <h3 data-lake-id="a7o8o" id="a7o8o"><span data-lake-id="ua02cb978" id="ua02cb978">无法适应分库分表</span></h3>
  <p data-lake-id="u2ff0f6f1" id="u2ff0f6f1"><br></p>
  <p data-lake-id="u5002019b" id="u5002019b"><span data-lake-id="u60cddac0" id="u60cddac0">当数据量大的时候，我们就要考虑分库分表了，但是在分库分表环境中，相关数据可能分布在不同的数据库中，外键通常难以跨越不同数据库来建立关系。更重要的是，分库分表环境中，数据的一致性可能更难维护。跨库事务搞不定。</span></p>
  <p data-lake-id="u144cfe03" id="u144cfe03"><br></p>
  <p data-lake-id="u431bf65f" id="u431bf65f"><br></p>
  <p data-lake-id="u57a0e0f4" id="u57a0e0f4"><span data-lake-id="uaeaa84bf" id="uaeaa84bf">以上，就是一些比较重要的原因吧。其实最主要的还是外键约束会带来一些额外的开销及锁竞争。而在很多大型互联网公司中，都是会尽量避免的。</span></p>
  <p data-lake-id="u412138a7" id="u412138a7"><span data-lake-id="u14c03b39" id="u14c03b39">​</span><br></p>
  <p data-lake-id="u8e40a87c" id="u8e40a87c"><span data-lake-id="u29be62b0" id="u29be62b0">就像大厂会使用RC来替代RR一样，会尽可能的降低锁的发生，一方面提升性能，一方面避免死锁。</span></p>
  <p data-lake-id="u60be7f72" id="u60be7f72"><span data-lake-id="u083236d4" id="u083236d4">​</span><br></p>
  <p data-lake-id="u64caf0d3" id="u64caf0d3"><br></p>
  <h3 data-lake-id="b3KWn" id="b3KWn"><span data-lake-id="ucb1e616a" id="ucb1e616a">逻辑删除</span></h3>
  <p data-lake-id="u14b5d6ef" id="u14b5d6ef"><span data-lake-id="u4b063a85" id="u4b063a85">​</span><br></p>
  <p data-lake-id="u15a1a762" id="u15a1a762"><span data-lake-id="u9cec017b" id="u9cec017b">我们在很多时候，都不会直接删除数据，而是通过逻辑删除进行，而外键在逻辑删除中会有一些约束。如：</span></p>
  <p data-lake-id="u065477dd" id="u065477dd"><span data-lake-id="u3db363f3" id="u3db363f3">​</span><br></p>
  <p data-lake-id="u2a378ffb" id="u2a378ffb"><span data-lake-id="uabb6b672" id="uabb6b672">1、外键是一种物理约束，而我们业务当中更多用的是逻辑删除。</span></p>
  <p data-lake-id="ubf1f1a09" id="ubf1f1a09"><span data-lake-id="u59667419" id="u59667419">2、加了外键之后必须先删子表，再删主表，而更多情况下都是先对主表做逻辑删除，再对子表做统一处理。</span></p>
 </body>
</html>